#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
// 3 idiots Add begin
#include "threads/vaddr.h" 	// for PHYS_BASE and etc..
#include "threads/init.h"	// power_off and etc..
#include "userprog/pagedir.h"
#include "filesys/filesys.h"
#include "userprog/process.h"
#include "threads/synch.h"
#include "devices/timer.h"
#include "devices/input.h"
#include "threads/vaddr.h"
#include "userprog/pagedir.h"
#include "filesys/file.h"
#include "threads/malloc.h"
#include "vm/swap.h"
#include "vm/frame.h"
// 3 idiots added begin
#include "lib/string.h"
#include "filesys/inode.h"
#include "filesys/free-map.h"
// 3 idiots added end

/* Process System Call */
static void sys_halt (void);
static void sys_exit (uint32_t *esp);
static tid_t sys_exec (uint32_t *esp);
static int sys_wait(uint32_t *esp);

/* File System Call */
static bool sys_create (uint32_t *esp);
static bool sys_remove (uint32_t *esp);
static int sys_open (uint32_t *esp);
static int sys_filesize (uint32_t *esp);
static int sys_read (uint32_t *esp);
static int sys_write (uint32_t *esp);
static void sys_seek (uint32_t *esp);
static unsigned sys_tell (uint32_t *esp);
static void sys_close (uint32_t *esp);
static void check_esp (void* esp);
struct file_node* get_fn_elem (int index);
void clear_fd_list (void);

/* Directory */
static bool sys_chdir (uint32_t *esp);
static bool sys_mkdir (uint32_t *esp);
static bool sys_readdir (uint32_t *esp);
static bool sys_isdir (uint32_t *esp);
static int sys_inumber (uint32_t *esp);
bool is_dir (int fd);

struct lock file_lock;
// 3 idiots Add end

/* This is a skeleton system call handler */

static void syscall_handler (struct intr_frame *);

static int p_count = 0;

void
syscall_init (void) 
{
  lock_init (&file_lock);
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}

static void
syscall_handler (struct intr_frame *f) 
{ 
  check_esp (f->esp);
  uint32_t *esp = f->esp;
  uint32_t syscall_num = *((uint32_t *)esp);

  switch (syscall_num)
  {
    case SYS_HALT :
	sys_halt();
	break;
    case SYS_EXIT :
        sys_exit (esp);
    	break;
    case SYS_EXEC :
        f->eax = sys_exec (esp);
    	break;
    case SYS_WAIT :
        f->eax = sys_wait (esp);
        break;
    case SYS_CREATE : /* Create a file. */
        f->eax = sys_create (esp);
        break;
    case SYS_REMOVE : /* Remove a file. */
        f->eax = sys_remove (esp);
        break;
    case SYS_OPEN :   /* Open a file. */
        f->eax = sys_open (esp);
	break;
    case SYS_FILESIZE : /* Get a filesize */
        f->eax = sys_filesize (esp);
	break;
    case SYS_READ :   /* Read a file */
        f->eax = sys_read (esp);
	break;
    case SYS_WRITE :  /* Write to a file. */
        f->eax = sys_write (esp);
    	break;
    case SYS_SEEK :
	sys_seek (esp);
	break;
    case SYS_TELL :
	f->eax = sys_tell (esp);	
	break;
    case SYS_CLOSE :  /* Close a file */
        sys_close(esp);
	break;
    case SYS_CHDIR:
	f->eax = sys_chdir (esp);
	break;
    case SYS_MKDIR:
        f->eax = sys_mkdir (esp);
	break;
    case SYS_READDIR:
        f->eax = sys_readdir (esp);
	break;
    case SYS_ISDIR:
	f->eax = sys_isdir (esp);
	break;
    case SYS_INUMBER:
	f->eax = sys_inumber (esp);
	break;
  }
}

static void sys_halt (void)
{
  power_off();
}

static void sys_exit (uint32_t *esp)
{
  check_esp (esp+1);
  int status = (int) *(esp+1);
  
  force_exit (status);
}

void force_exit (int status)
{
  clear_fd_list ();
  p_count--;
  printf("%s: exit(%d)\n", thread_current ()->userprog_name, status);
  thread_current()->ret_value = status;
  thread_exit();
}

static int sys_wait (uint32_t *esp)
{
  check_esp (esp+1);
  tid_t tid = (tid_t) *(esp+1);
  return process_wait (tid);
}

static tid_t sys_exec (uint32_t *esp)
{
  check_esp (esp+1);
  char *cmd_line = (char *) *(esp+1);
  tid_t ret_value = -1;

  if (cmd_line == NULL || !is_user_vaddr(cmd_line) || !pagedir_get_page(thread_current ()->pagedir, cmd_line))
  {
     force_exit (-1);
  }
  else if(p_count < MAX_PROCESS_CNT)
  {
     tid_t rv = process_execute (cmd_line);

     if (rv != TID_ERROR)
     {
        p_count++;
        ret_value = rv;
     }
  }
  
  return ret_value;
}

static bool sys_create (uint32_t *esp)
{
  check_esp (esp+1);
  check_esp (esp+2);

  char *file_path = (char *) *(esp+1);
  uint32_t file_size = (uint32_t) *(esp+2);
  bool ret_value = false;
	
  if (file_path == NULL || !is_user_vaddr (file_path) || !pagedir_get_page (thread_current ()->pagedir, file_path)) 
     force_exit (-1);
  else
  {
     lock_acquire (&file_lock);
     ret_value = filesys_create (file_path, file_size, false);
     lock_release (&file_lock);
  }

  return ret_value;
}

static bool sys_remove (uint32_t *esp)
{
  check_esp (esp+1);

  char *file_path = (char *) *(esp+1);
  bool ret_value = false;
	
  if (file_path == NULL || !is_user_vaddr (file_path) || !pagedir_get_page (thread_current ()->pagedir, file_path)) 
     force_exit (-1);   
  else 
  {
     lock_acquire (&file_lock);
     ret_value = filesys_remove (file_path);
     lock_release (&file_lock);
  }

  return ret_value;
}

static int sys_open (uint32_t *esp)
{
  check_esp (esp+1);

  char *file_path = (char *) *(esp+1);
  struct file* temp_file = NULL;

  if (file_path == NULL || !is_user_vaddr (file_path) || !pagedir_get_page (thread_current ()->pagedir, file_path))
     force_exit (-1);
  else
  {
     lock_acquire (&file_lock);
     temp_file = filesys_open (file_path);
     lock_release (&file_lock);
  }

  if (temp_file == NULL)
  {
     return -1;
  }
  else
  {
     struct file_node *new_fn;
    
     new_fn = malloc (sizeof(struct file_node));
     new_fn->fd = 2 + list_size (&(thread_current ()->fd_list));
     if(new_fn->fd >= 130)
     {
       free (new_fn);
       return -1;
     }
     new_fn->pfile = temp_file;
     list_push_back (&thread_current ()->fd_list, &new_fn->elem);
     
     return new_fn->fd;
  }
}

static int sys_read (uint32_t *esp) 
{
   check_esp (esp+2);
   check_esp (esp+3);

   int fd = (int) *(esp+1);
   char* r_buffer = (char *) *(esp+2);
   unsigned r_size = (unsigned) *(esp+3);
   int ret_value = -1;

   if (r_buffer == NULL || !is_user_vaddr(r_buffer) || !pagedir_get_page(thread_current ()->pagedir, r_buffer)) 
     force_exit (-1);

   // read from console
   if (fd == 0) 
   {
       unsigned u;

       for (u = 0; u < r_size; u++)
       {
          r_buffer[u] = input_getc ();
       }
       ret_value = r_size;
   }
   else if (fd > 1) 
   {
       // read from file
       struct file_node* fn_elem = get_fn_elem (fd);

       if (fn_elem == NULL)
          ret_value = -1;
       else 
       {
          lock_acquire (&file_lock);
          ret_value = file_read(fn_elem->pfile, r_buffer, r_size);
          lock_release (&file_lock);
       }
   }

   return ret_value;
}

static int sys_write (uint32_t *esp) 
{
   check_esp (esp+3);

   int fd = (int) *(esp+1);
   char* w_buffer = (char *) *(esp+2);
   unsigned w_size = (unsigned) *(esp+3);
   int ret_value = -1;

   if (w_buffer == NULL || !is_user_vaddr (w_buffer) || !pagedir_get_page (thread_current ()->pagedir, w_buffer))
      force_exit (-1);
   if (w_size <= 0)
      return 0;
   
   if (fd == 1) 
   {
      // write to console
      putbuf(w_buffer, w_size);
      ret_value = w_size;
   } 
   else if (fd > 1) 
   {
      // Write from file
      struct file_node* fn_elem = get_fn_elem(fd);

      if (fn_elem != NULL)
      {
         lock_acquire (&file_lock);
         ret_value = file_write (fn_elem->pfile, w_buffer, w_size);
         lock_release (&file_lock);
      }
   }
   
   return ret_value;
}

static void sys_close (uint32_t *esp)
{
   check_esp (esp+1);
   int fd = (int) *(esp+1);
  
   struct file_node* fn_elem = get_fn_elem(fd);

   if (fn_elem != NULL)
   {
      lock_acquire (&file_lock);
      file_close (fn_elem->pfile);
      lock_release (&file_lock);
      list_remove (&fn_elem->elem);
      free (fn_elem);
   }
}

static int sys_filesize (uint32_t *esp)
{
   int fd = (int) *(esp+1);
   struct file_node* fn_elem = get_fn_elem(fd);

   if (fn_elem == NULL)
   {
     return -1;
   }
   else
   {
     return file_length (fn_elem->pfile);
   }
}

static void sys_seek (uint32_t *esp)
{
   int fd = (int) *(esp+1);
   unsigned position = (unsigned) *(esp+2);

   if (fd >= 2)
   {
     struct file_node *fn_elem = get_fn_elem (fd);

     if (fn_elem != NULL)
     {
        file_seek (fn_elem->pfile, position);	
     }
   }
}

static unsigned sys_tell (uint32_t *esp)
{
    int fd = (int) *(esp+1);

    if( fd < 2 )
    {
	return -1;
    }
    else 
    {
	struct file_node *fn_elem = get_fn_elem (fd);
    	return file_tell (fn_elem->pfile);
    }
}

static void check_esp (void* esp)
{
  if (is_user_vaddr (esp) == 0)
  {
    force_exit (-1);
  }
  if (pagedir_get_page ((uint32_t *)thread_current ()->pagedir, esp) == NULL)
  {
    force_exit (-1);
  }
}

struct file_node* get_fn_elem (int index)
{
  struct list_elem *temp_elem = list_begin(&thread_current()->fd_list);

  for (; temp_elem != list_end(&thread_current()->fd_list); temp_elem = list_next(temp_elem)) 
  {
     struct file_node *fn_elem = list_entry (temp_elem, struct file_node, elem);

     if (fn_elem->fd == index)
     {
        return fn_elem;
     }
  }
  
  return NULL;
}

void clear_fd_list (void)
{
  struct list_elem *temp_elem = NULL;

  while (!list_empty (&thread_current ()->fd_list))
  {
    temp_elem = list_pop_front (&thread_current ()->fd_list);
    struct file_node *fn_elem = list_entry (temp_elem, struct file_node, elem);

    lock_acquire (&thread_current()->f_lock);
    file_close (fn_elem->pfile);
    free(fn_elem);
    lock_release (&thread_current()->f_lock);
  }
}

// Change Directory
static bool sys_chdir (uint32_t *esp) 
{
    lock_acquire (&file_lock);
    check_esp (esp+1);
    const char* dir = (const char*) *(esp+1);
    
    // Get Directory
    struct dir* temp_dir = dir_get_directory (dir);

    // Change Directory
    if (temp_dir != NULL)
    {
	dir_close (thread_get_current_dir ());
	thread_set_current_dir (temp_dir);
        lock_release (&file_lock);
	return true;
    }
    lock_release (&file_lock);
    return false;
}

// Make Directory
static bool sys_mkdir (uint32_t *esp) 
{
    lock_acquire (&file_lock);
    const char* dir = (const char*) *(esp+1);
    bool ret_value = filesys_create (dir, 0, true);
    lock_release (&file_lock);
    return ret_value;
}

// Read Directory
static bool sys_readdir (uint32_t *esp) 
{
    lock_acquire (&file_lock);	
    int fd = (int) *(esp+1);
    char* file_name = (char*) *(esp+2);

    // if it is not directory return false
    if (!is_dir(fd)) 
    {
	return false;
    }
    
    check_esp ((void *) file_name);
    check_esp ((void *) file_name + NAME_MAX + 1);

    // Get file
    struct file_node* fn_elem = get_fn_elem (fd);
    struct file* file = fn_elem->pfile;
	
    // create local directory copy from file
    struct dir temp_dir;
    temp_dir.inode = file->inode;
    temp_dir.pos = file->pos;

    bool success = dir_readdir (&temp_dir, file_name);
	
    // set file pos
    if (success)
    {
       file->pos = temp_dir.pos;
    }
    lock_release (&file_lock);
    return success;
}

// Check it is Dir or not
static bool sys_isdir (uint32_t *esp) 
{
    // Get file.
    int fd = (int) *(esp+1);
    return is_dir (fd);
}

bool is_dir (int fd)
{
    struct file_node* fn_elem = get_fn_elem (fd);
    struct file* file = fn_elem->pfile;

    if (file == NULL) 
    {
       return false;
    }

    struct inode* temp_inode = file_get_inode (file);

    if (temp_inode == NULL) 
    {
       return false;
    }

    if (inode_is_dir (temp_inode)) 
    {
       return true;
    }
    else 
    {
       return false;
    }
}

static int sys_inumber (uint32_t *esp) 
{
    // Get File
    int fd = (int) *(esp+1);
    struct file_node* fn_elem = get_fn_elem (fd);
    struct file* file = fn_elem->pfile;

    if (file == NULL) 
    {
       return -1;
    }
	
    struct inode* temp_inode = file_get_inode (file);

    if (temp_inode == NULL) 
    {
       return -1;
    }

    return inode_get_inumber(temp_inode);
}
